Setup & Data Load
# load packages
library(readxl)
library(ggplot2)
library(lubridate)
library(here)
library(scales)
library(dplyr)
library(plotly)
require(knitr)
# Setting locale
Sys.setlocale("LC_ALL", 'German_Switzerland')
[1] "LC_COLLATE=German_Switzerland.1252;LC_CTYPE=German_Switzerland.1252;LC_MONETARY=German_Switzerland.1252;LC_NUMERIC=C;LC_TIME=German_Switzerland.1252"
LC_COLLATE=German_Switzerland.1252;LC_CTYPE=German_Switzerland.1252;LC_MONETARY=German_Switzerland.1252;LC_NUMERIC=C;LC_TIME=German_Switzerland.1252
# Set working directory for R and knitr
setwd(here('Group_Work'))
opts_knit$set(root.dir = here('Group_Work'))
# Get rental data
bikedata.source <- read.csv2('./source/bikesharing/SeoulBikeData.csv', sep=',')
str(bikedata.source)
'data.frame': 8760 obs. of 14 variables:
$ Date : chr "01/12/2017" "01/12/2017" "01/12/2017" "01/12/2017" ...
$ Rented.Bike.Count : int 254 204 173 107 78 100 181 460 930 490 ...
$ Hour : int 0 1 2 3 4 5 6 7 8 9 ...
$ Temperature..C. : chr "-5.2" "-5.5" "-6" "-6.2" ...
$ Humidity... : int 37 38 39 40 36 37 35 38 37 27 ...
$ Wind.speed..m.s. : chr "2.2" "0.8" "1" "0.9" ...
$ Visibility..10m. : int 2000 2000 2000 2000 2000 2000 2000 2000 2000 1928 ...
$ Dew.point.temperature..C.: chr "-17.6" "-17.6" "-17.7" "-17.6" ...
$ Solar.Radiation..MJ.m2. : chr "0" "0" "0" "0" ...
$ Rainfall.mm. : chr "0" "0" "0" "0" ...
$ Snowfall..cm. : chr "0" "0" "0" "0" ...
$ Seasons : chr "Winter" "Winter" "Winter" "Winter" ...
$ Holiday : chr "No Holiday" "No Holiday" "No Holiday" "No Holiday" ...
$ Functioning.Day : chr "Yes" "Yes" "Yes" "Yes" ...
As the output above shows, the bike rental data from Seoul (source: https://archive.ics.uci.edu/ml/datasets/Seoul+Bike+Sharing+Demand) contains over 8700 observations and 14 variables or attributes. This includes hourly number of bike rentals including meteorological information - such as humidity, temperature, rainfall etc. - as well as ‘holiday’ indicators. R tried to automatically determine the data types of our attributes but mis-categorized some. All attributes will be cast to their required data type in the next step (Data Transformation).
# Get airport arrival data 2017 & 2018
air17.source <- read_excel('./source/bikesharing/Airport_Statistics_Stat_by_Time_of_Day_201701_201712_edited.xls', sheet = 1)
air18.source <- read_excel('./source/bikesharing/Airport_Statistics_Stat_by_Time_of_Day_201801_201812_edited.xls', sheet = 1)
str(air17.source) #str(air18.source) # same as air17 but for year 2018
tibble [25 x 4] (S3: tbl_df/tbl/data.frame)
$ time : chr [1:25] "00:00 ~ 00:59" "01:00 ~ 01:59" "02:00 ~ 02:59" "03:00 ~ 03:59" ...
$ Arrival : chr [1:25] "109,770" "28,682" "46,792" "179,620" ...
$ Departure: chr [1:25] "498,707" "254,771" "64,177" "18,400" ...
$ Total : chr [1:25] "608,477" "283,453" "110,969" "198,020" ...
In addition to the above bike rental data set, we will employ data from the Incheon Airport Seoul (source: https://www.airport.kr/co/en/cpr/statisticCategoryOfTime.do) which lists the total number of passengers (arrivals) for 2017 and 2018, grouped by hour of the day. This grouping was chosen due to the fact that the available bike data is also grouped on an hourly basis.
Data Transformation
In order to start analysing and building/validating models on this data, the available files and attributes need to be transformed into a suitable, standardise and combined form. Data transformation includes the following activities, depending on the specific dataset(s):
- casting data to correct types especially
- factors
- date/time
- numeric values (int, double)
- remove invalid values / corrupted lines
- standardise missing values / NAs
- add computed / derived columns such as
- hour extracted from field ‘time’ in air dataset to link to bike data
- relative number of passengers per hour in air dataset
- combine both datasets
Let us start with the rental bike dataset. Below we will cast the attributes to their most appropriate type. Potential issues with missing / invalid data should be revealed during that process.
Missing Values
Before we continue to the further preparation steps, now would be a good time to check for missing values and handle them appropriately (before merging all datasets into one final dataframe). We use is.na() and a search for empty strings to identify potentially missing values. If feasible and necessary, further imputations will be made to replace those missing values. The output of the various function calls below is suppressed but did not show any missing values or empty strings.
# bike data set
bikedata.source[is.na(bikedata.source),]
# ==> no NA values!
bikedata.source[bikedata.source == '',]
# ==> no empty strings!
# air 2017 dataset
air17.source[is.na(air17.source)]
# ==> no NA values!
air17.source == ''
# ==> no empty strings!
# air 2018 dataset
is.na(air18.source)
# ==> no NA values!
air18.source == ''
# ==> no empty strings!
Casting of Data Types
First, let’s cast the attributes of bikedata into their correct data types:
# creating cleaned version of source data with correctly cast data types for each attribute
bikedata.c <- data.frame(
Date = as.Date(bikedata.source$Date, format='%d/%m/%Y'),
Year = year(as.Date(bikedata.source$Date, format='%d/%m/%Y')),
RentCount = bikedata.source$Rented.Bike.Count,
Hour = bikedata.source$Hour,
Temperature = as.double(bikedata.source$Temperature..C., length = 1),
Humidity = bikedata.source$Humidity...,
Windspeed = as.double(bikedata.source$Wind.speed..m.s., length = 1),
Visibility = bikedata.source$Visibility..10m.,
DewPointTemp = as.double(bikedata.source$Dew.point.temperature..C., length = 2),
SolarRadiation= as.double(bikedata.source$Solar.Radiation..MJ.m2., length = 1),
Rainfall = as.double(bikedata.source$Rainfall.mm., length = 1),
Snowfall = as.double(bikedata.source$Snowfall..cm., length = 1),
Season = factor(bikedata.source$Seasons),
Holiday = factor(bikedata.source$Holiday),
Functional = factor(bikedata.source$Functioning.Day)
)
# check
str(bikedata.c)
'data.frame': 8760 obs. of 15 variables:
$ Date : Date, format: "2017-12-01" "2017-12-01" ...
$ Year : num 2017 2017 2017 2017 2017 ...
$ RentCount : int 254 204 173 107 78 100 181 460 930 490 ...
$ Hour : int 0 1 2 3 4 5 6 7 8 9 ...
$ Temperature : num -5.2 -5.5 -6 -6.2 -6 -6.4 -6.6 -7.4 -7.6 -6.5 ...
$ Humidity : int 37 38 39 40 36 37 35 38 37 27 ...
$ Windspeed : num 2.2 0.8 1 0.9 2.3 1.5 1.3 0.9 1.1 0.5 ...
$ Visibility : int 2000 2000 2000 2000 2000 2000 2000 2000 2000 1928 ...
$ DewPointTemp : num -17.6 -17.6 -17.7 -17.6 -18.6 -18.7 -19.5 -19.3 -19.8 -22.4 ...
$ SolarRadiation: num 0 0 0 0 0 0 0 0 0.01 0.23 ...
$ Rainfall : num 0 0 0 0 0 0 0 0 0 0 ...
$ Snowfall : num 0 0 0 0 0 0 0 0 0 0 ...
$ Season : Factor w/ 4 levels "Autumn","Spring",..: 4 4 4 4 4 4 4 4 4 4 ...
$ Holiday : Factor w/ 2 levels "Holiday","No Holiday": 2 2 2 2 2 2 2 2 2 2 ...
$ Functional : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2 2 2 ...
The casting of data types did not show any problems with invalid data / missing values so no removal / cleanup of that dataset seems to be necessary at this point. In the next step we will continue with the two air passenger datasets for Incheon Airport. This dataset is split by year. After casting all attributes to appropriate data types, the two data sets (2017 and 2018) will be combined into one dataframe with the addition of a year attribute.
# before going further, we need to remove the 'TOTAL' row from the data set
air17.edit <- air17.source[air17.source$time != 'Total',]
air18.edit <- air18.source[air18.source$time != 'Total',]
# 2017 air passenger data Seoul
air17.c <- data.frame(
Year = 2017,
Hour = as.integer(substring(air17.edit$time, 1, 2)),
Arrival = as.integer(gsub(',', '', air17.edit$Arrival)),
Departure = as.integer(gsub(',', '', air17.edit$Departure)),
TotalArrival = as.integer(gsub(',', '', air17.source$Arrival[length(air17.source$Arrival)])), # get total arrivals for 2017
TotalDeparture= as.integer(gsub(',', '', air17.source$Departure[length(air17.source$Departure)])) # get total departures for 2017
)
# 2018 air passenger data Seoul
air18.c <- data.frame(
Year = 2018,
Hour = as.integer(substring(air18.edit$time, 1, 2)),
Arrival = as.integer(gsub(',', '', air18.edit$Arrival)),
Departure = as.integer(gsub(',', '', air18.edit$Departure)),
TotalArrival = as.integer(gsub(',', '', air18.source$Arrival[length(air18.source$Arrival)])), # get total arrivals for 2018
TotalDeparture= as.integer(gsub(',', '', air18.source$Departure[length(air18.source$Departure)])) # get total departures for 2018
)
Data Merge
As a last step in the transformation phase, the currently separate datasets air.17, air.18 and bikedata will be merged together to create one single data frame to be used for the further analysis. In a first step, the two *air** datasets will be merged, before then joining them to the bikedata dataset.
# combine air17 and air 18 into air.c
air.c <- rbind(air17.c, air18.c)
# adding relative calculations (percentage of total yearly arrivals/departures per hour)
ArrivalPct <- air.c$Arrival / air.c$TotalArrival
DeparturePct <- air.c$Departure / air.c$TotalDeparture
air.c <- cbind(air.c, ArrivalPct, DeparturePct)
# result check
head(air.c)
The air passenger data sets required a bit more attribute engineering. We first had to remove the TOTAL row from both files (2017 & 2018). After that, we added a Year attribute to enable us to latter merge the two data sets. We then isolated a substring of the time description (used to be in the form of “00:00 ~ 00:59”) to standardise the values to the same values found in the bike sharing data set ( integers from 0 to 23 ). We then had to replace “,” characters found in the total Arrival and Departure counts before being able to cast them to integer. Further, two new attributes TotalArrival and TotalDeparture were added to both the 2017 and 2018 data which hold the total arrivals and departures per year respectively. This information will be used to calculate the percentage of arrivals/departures per hour compared to the total arrivals/departures per year. The last step combined the two year 2017 and 2018 into one data frame air.c.
The next step will merge the air passenger information with the bike sharing data to create one data frame as the basis for further analysis and modelling.
# merging the two data frames based on 'Year' and 'Hour)'
data.m <- merge(bikedata.c, air.c, by = c('Year','Hour'), all = TRUE)
# sorting the result by 'Date' and 'Hour'
data.m <- data.m[order(data.m$Date, data.m$Hour),]
# persist resulting (basis) dataframe for further analysis via serialisation
saveRDS(data.m, file='./db/data.RDS')
Exploratory Analysis
setwd(here('Group_Work'))
data <- readRDS('./db/data.RDS')
head(data)
Let us know create various graphs to get insights to the data. We are interested to see how the bike rental count developed in the months from end of 2017 to end of 2018.
#head(data)
data$Month <- as.Date(cut(data$Date,
breaks = "month"))
ggplot(data = data, mapping = aes(x=Month, y=RentCount))+
stat_summary(fun = sum, geom = "line", color="blue")+
labs(title="Bike Rental Monthly", y="Bike Rentals")+
scale_x_date(labels = date_format("%m/%y"), breaks = "1 month")

Clearly a seasonality is visible in the data. Let’s compare the different seasons by creating boxplots for each season.
#This shows the hourly data not daily
ggplot(data = data, mapping = aes(x=Season, y = RentCount)) +
geom_boxplot()+
labs(y = "Bike Rentals")

As expected a seasonality of bike rentals is visible. More bike rentals occur during the summer months compared to the winter. Autumn and spring show similar distribution.
Let’s see how the rental count behaves during a day. Are certain hours preferred for renting a bike? In order to get a first answer to the question we will average over the hours.
ggplot(data = data, mapping = aes(x=as.factor(Hour), y=RentCount))+
stat_summary(fun="mean", geom="bar")+
labs(y = "Bike Rentals", x = "Hour")

We assume that the temperature influences the number of bikes rented. Let’s create a scatterplot comparing those two variables.
#Plot the hourly count values based on recorded temperatures (in °C)
ggplot(data = data, mapping = aes(y = RentCount, x = Temperature)) +
geom_point()+
geom_smooth(se = FALSE)

The resulting plot shows an interesting relationship between temperature and the hourly rental count:
- There seems to be a strongly positive trend between -20 °C (almost 0 rentals) up to around +27/28 °C (average of around 1’000 rentals)
- After that, a clear decline in the average hourly rental count is visible beyond +27/28 °C
This seems inherently explainable since people are less likely to use a bicycle in full summer heat and to prefer alternative modes of transport (preferably with A/C).
Linear Modelling
Implementing first (simple) Model
As we are looking at count data it makes sense to use a generalised linear model (GLM) in order to specify the distribution of our data as a poisson and apply the appropriate link function (logarithm). In a first, simple model we will look into the effect of the season categorical variable (factor in R) onto the target variable rental count.
glm.bike <- glm(RentCount ~ Season,
family = "poisson",
data = data)
glm.bike
Call: glm(formula = RentCount ~ Season, family = "poisson", data = data)
Coefficients:
(Intercept) SeasonSpring SeasonSummer SeasonWinter
6.7088 -0.1157 0.2324 -1.2903
Degrees of Freedom: 8759 Total (i.e. Null); 8756 Residual
Null Deviance: 4979000
Residual Deviance: 3682000 AIC: 3749000
# Absolute count numbers per model
paste( 'Intercept (Autumn) = ', exp(coef(glm.bike))[1],
'\nSpring Absolute Count = ', round(exp(coef(glm.bike))[1] * exp(coef(glm.bike))[2],0),
'\nSummer Absolute Count = ', round(exp(coef(glm.bike))[1] * exp(coef(glm.bike))[3], 0),
'\nWinter Absolute Count = ', round(exp(coef(glm.bike))[1] * exp(coef(glm.bike))[4], 0)
)
[1] "Intercept (Autumn) = 819.597985348541 \nSpring Absolute Count = 730 \nSummer Absolute Count = 1034 \nWinter Absolute Count = 226"
Intercept (Autumn) = 819.597985348541
Spring Absolute Count = 730
Summer Absolute Count = 1034
Winter Absolute Count = 226
As suspected when investigating the boxplots based on season, temperature obviously has a significant impact on the bike rental count. However, as also evident in these results, there seems to be only a relatively minor difference between autumn, spring and summer (\(819\), \(0.9 * 819 = 730\), \(1.26 * 810 = 1034\)). The only relatively large differences occurs between these three seasons and winter (\(0.28 * 819 = 226\)).
Extended Modelling
[CHAPTER OF CHOICE] - Exploring plotly
For the chapter of choice - with the aim to use a package not mentioned in the course - we will explore the plotly library. This library allows to embed advanced, interactive charts in markdown by using the plotly.js JavaScript library. The documentation for this library can be found here: https://plotly.com/r/
Lets start with a simple, multi-dimensional plot using the plot_ly() function. Per default, plotly graphs allow for interactive selections and filtering, which is very nice!
#library(plotly) #install.packages('plotly')
fig <- plot_ly(data=data,
x=~Temperature,
y=~Hour,
z=~RentCount,
type="scatter3d", mode="markers")
fig
That does not seem too bad! We have quickly created an interactive 3D plot which allows us to change the perspective and hover over single data points. However, the plot area is rather small and there are many data points to be displayed. In the current form, its hard to distinguish the separate points. Let’s see if we can adjust the plot area accordingly and create more room for the 3D plot to shine. In order to show case interactive capabilities of plotly, we will further add a categorical variable and use this to color the different data points. By doing so, plotly will automatically add a filter and allow to select different levels of the factor variable.
fig2 <- plot_ly(data=data,
x=~Temperature,
y=~Hour,
z=~RentCount,
type="scatter3d", mode="markers", color=~Season #here, we added the color argument and set it to the 'Season' factor
)
# in order to adjust the plot area, we have to adjust the width, height and margins of the graph as well as deactivate autosize
fig2 %>% layout(autosize = FALSE, width = 1000, height = 600, margin = list(l=50, r=10, b=10, t=10, pad=1))
Now this seems a lot better!
LS0tDQp0aXRsZTogIlItQm9vdGNhbXAgQW5hbHlzaXMgLSBSZW50YWwgQmlrZSBCZWhhdmlvdXIgU2VvdWwiDQphdXRob3I6IEtvbnN0YW50aW5vcyBMZXNzaXMsIFBhc2NhbCBIaW1tZWxiZXJnZXINCmRhdGU6IDA4LzA5LzIwMjANCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQo8Y2VudGVyPg0KIVtCaWtlIFJlbnRhbF0oLi9pbWcvcmVudGFsLmpwZykNCjwvY2VudGVyPg0KDQojIEJhY2tncm91bmQNCg0KdGVzdA0KDQojIFNldHVwICYgRGF0YSBMb2FkDQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0V9DQojIGxvYWQgcGFja2FnZXMNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KHNjYWxlcykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBsb3RseSkNCnJlcXVpcmUoa25pdHIpDQoNCg0KIyBTZXR0aW5nIGxvY2FsZQ0KU3lzLnNldGxvY2FsZSgiTENfQUxMIiwgJ0dlcm1hbl9Td2l0emVybGFuZCcpDQoNCiMgU2V0IHdvcmtpbmcgZGlyZWN0b3J5IGZvciBSIGFuZCBrbml0cg0Kc2V0d2QoaGVyZSgnR3JvdXBfV29yaycpKQ0Kb3B0c19rbml0JHNldChyb290LmRpciA9IGhlcmUoJ0dyb3VwX1dvcmsnKSkNCg0KIyBHZXQgcmVudGFsIGRhdGENCmJpa2VkYXRhLnNvdXJjZSA8LSByZWFkLmNzdjIoJy4vc291cmNlL2Jpa2VzaGFyaW5nL1Nlb3VsQmlrZURhdGEuY3N2Jywgc2VwPScsJykNCnN0cihiaWtlZGF0YS5zb3VyY2UpDQpgYGANCkFzIHRoZSBvdXRwdXQgYWJvdmUgc2hvd3MsIHRoZSAqKmJpa2UgcmVudGFsIGRhdGEgZnJvbSBTZW91bCoqIChzb3VyY2U6IGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9TZW91bCtCaWtlK1NoYXJpbmcrRGVtYW5kKSBjb250YWlucyBvdmVyIDg3MDAgb2JzZXJ2YXRpb25zIGFuZCAxNCB2YXJpYWJsZXMgb3IgYXR0cmlidXRlcy4gVGhpcyBpbmNsdWRlcyBob3VybHkgbnVtYmVyIG9mIGJpa2UgcmVudGFscyBpbmNsdWRpbmcgbWV0ZW9yb2xvZ2ljYWwgaW5mb3JtYXRpb24gLSBzdWNoIGFzIGh1bWlkaXR5LCB0ZW1wZXJhdHVyZSwgcmFpbmZhbGwgZXRjLiAtICBhcyB3ZWxsIGFzICdob2xpZGF5JyBpbmRpY2F0b3JzLg0KUiB0cmllZCB0byBhdXRvbWF0aWNhbGx5IGRldGVybWluZSB0aGUgZGF0YSB0eXBlcyBvZiBvdXIgYXR0cmlidXRlcyBidXQgbWlzLWNhdGVnb3JpemVkIHNvbWUuIEFsbCBhdHRyaWJ1dGVzIHdpbGwgYmUgY2FzdCB0byB0aGVpciByZXF1aXJlZCBkYXRhIHR5cGUgaW4gdGhlIG5leHQgc3RlcCAoKipEYXRhIFRyYW5zZm9ybWF0aW9uKiopLg0KDQpgYGB7ciwgY2FjaGU9VFJVRX0NCg0KIyBHZXQgYWlycG9ydCBhcnJpdmFsIGRhdGEgMjAxNyAmIDIwMTgNCmFpcjE3LnNvdXJjZSA8LSByZWFkX2V4Y2VsKCcuL3NvdXJjZS9iaWtlc2hhcmluZy9BaXJwb3J0X1N0YXRpc3RpY3NfU3RhdF9ieV9UaW1lX29mX0RheV8yMDE3MDFfMjAxNzEyX2VkaXRlZC54bHMnLCBzaGVldCA9IDEpDQphaXIxOC5zb3VyY2UgPC0gcmVhZF9leGNlbCgnLi9zb3VyY2UvYmlrZXNoYXJpbmcvQWlycG9ydF9TdGF0aXN0aWNzX1N0YXRfYnlfVGltZV9vZl9EYXlfMjAxODAxXzIwMTgxMl9lZGl0ZWQueGxzJywgc2hlZXQgPSAxKQ0Kc3RyKGFpcjE3LnNvdXJjZSkgI3N0cihhaXIxOC5zb3VyY2UpICMgc2FtZSBhcyBhaXIxNyBidXQgZm9yIHllYXIgMjAxOA0KDQpgYGANCkluIGFkZGl0aW9uIHRvIHRoZSBhYm92ZSBiaWtlIHJlbnRhbCBkYXRhIHNldCwgd2Ugd2lsbCBlbXBsb3kgZGF0YSBmcm9tIHRoZSAqKkluY2hlb24gQWlycG9ydCBTZW91bCoqIChzb3VyY2U6IGh0dHBzOi8vd3d3LmFpcnBvcnQua3IvY28vZW4vY3ByL3N0YXRpc3RpY0NhdGVnb3J5T2ZUaW1lLmRvKSB3aGljaCBsaXN0cyB0aGUgdG90YWwgbnVtYmVyIG9mIHBhc3NlbmdlcnMgKGFycml2YWxzKSBmb3IgMjAxNyBhbmQgMjAxOCwgZ3JvdXBlZCBieSBob3VyIG9mIHRoZSBkYXkuIFRoaXMgZ3JvdXBpbmcgd2FzIGNob3NlbiBkdWUgdG8gdGhlIGZhY3QgdGhhdCB0aGUgYXZhaWxhYmxlIGJpa2UgZGF0YSBpcyBhbHNvIGdyb3VwZWQgb24gYW4gaG91cmx5IGJhc2lzLg0KDQoNCiMgRGF0YSBUcmFuc2Zvcm1hdGlvbg0KDQpJbiBvcmRlciB0byBzdGFydCBhbmFseXNpbmcgYW5kIGJ1aWxkaW5nL3ZhbGlkYXRpbmcgbW9kZWxzIG9uIHRoaXMgZGF0YSwgdGhlIGF2YWlsYWJsZSBmaWxlcyBhbmQgYXR0cmlidXRlcyBuZWVkIHRvIGJlIHRyYW5zZm9ybWVkIGludG8gYSBzdWl0YWJsZSwgc3RhbmRhcmRpc2UgYW5kIGNvbWJpbmVkIGZvcm0uIERhdGEgdHJhbnNmb3JtYXRpb24gaW5jbHVkZXMgdGhlIGZvbGxvd2luZyBhY3Rpdml0aWVzLCBkZXBlbmRpbmcgb24gdGhlIHNwZWNpZmljIGRhdGFzZXQocyk6DQoNCiogY2FzdGluZyBkYXRhIHRvIGNvcnJlY3QgdHlwZXMgZXNwZWNpYWxseQ0KICAgICsgZmFjdG9ycw0KICAgICsgZGF0ZS90aW1lDQogICAgKyBudW1lcmljIHZhbHVlcyAoaW50LCBkb3VibGUpDQoqIHJlbW92ZSBpbnZhbGlkIHZhbHVlcyAvIGNvcnJ1cHRlZCBsaW5lcw0KKiBzdGFuZGFyZGlzZSBtaXNzaW5nIHZhbHVlcyAvIE5Bcw0KKiBhZGQgY29tcHV0ZWQgLyBkZXJpdmVkIGNvbHVtbnMgc3VjaCBhcw0KICAgICsgaG91ciBleHRyYWN0ZWQgZnJvbSBmaWVsZCAndGltZScgaW4gYWlyIGRhdGFzZXQgdG8gbGluayB0byBiaWtlIGRhdGENCiAgICArIHJlbGF0aXZlIG51bWJlciBvZiBwYXNzZW5nZXJzIHBlciBob3VyIGluIGFpciBkYXRhc2V0DQoqIGNvbWJpbmUgYm90aCBkYXRhc2V0cw0KDQpMZXQgdXMgc3RhcnQgd2l0aCB0aGUgcmVudGFsIGJpa2UgZGF0YXNldC4gQmVsb3cgd2Ugd2lsbCBjYXN0IHRoZSBhdHRyaWJ1dGVzIHRvIHRoZWlyIG1vc3QgYXBwcm9wcmlhdGUgdHlwZS4gUG90ZW50aWFsIGlzc3VlcyB3aXRoIG1pc3NpbmcgLyBpbnZhbGlkIGRhdGEgc2hvdWxkIGJlIHJldmVhbGVkIGR1cmluZyB0aGF0IHByb2Nlc3MuDQoNCg0KDQojIyMgTWlzc2luZyBWYWx1ZXMNCg0KQmVmb3JlIHdlIGNvbnRpbnVlIHRvIHRoZSBmdXJ0aGVyIHByZXBhcmF0aW9uIHN0ZXBzLCBub3cgd291bGQgYmUgYSBnb29kIHRpbWUgdG8gY2hlY2sgZm9yIG1pc3NpbmcgdmFsdWVzIGFuZCBoYW5kbGUgdGhlbSBhcHByb3ByaWF0ZWx5IChiZWZvcmUgbWVyZ2luZyBhbGwgZGF0YXNldHMgaW50byBvbmUgZmluYWwgZGF0YWZyYW1lKS4gV2UgdXNlICppcy5uYSgpKiBhbmQgYSBzZWFyY2ggZm9yIGVtcHR5IHN0cmluZ3MgdG8gaWRlbnRpZnkgcG90ZW50aWFsbHkgbWlzc2luZyB2YWx1ZXMuIElmIGZlYXNpYmxlIGFuZCBuZWNlc3NhcnksIGZ1cnRoZXIgaW1wdXRhdGlvbnMgd2lsbCBiZSBtYWRlIHRvIHJlcGxhY2UgdGhvc2UgbWlzc2luZyB2YWx1ZXMuDQpUaGUgb3V0cHV0IG9mIHRoZSB2YXJpb3VzIGZ1bmN0aW9uIGNhbGxzIGJlbG93IGlzIHN1cHByZXNzZWQgYnV0IGRpZCAqKm5vdCBzaG93IGFueSBtaXNzaW5nIHZhbHVlcyBvciBlbXB0eSBzdHJpbmdzKiouDQoNCmBgYHtyLCBjYWNoZT1UUlVFLCByZXN1bHRzPUZBTFNFfQ0KIyBiaWtlIGRhdGEgc2V0DQpiaWtlZGF0YS5zb3VyY2VbaXMubmEoYmlrZWRhdGEuc291cmNlKSxdDQojID09PiBubyBOQSB2YWx1ZXMhDQpiaWtlZGF0YS5zb3VyY2VbYmlrZWRhdGEuc291cmNlID09ICcnLF0NCiMgPT0+IG5vIGVtcHR5IHN0cmluZ3MhDQoNCiMgYWlyIDIwMTcgZGF0YXNldA0KYWlyMTcuc291cmNlW2lzLm5hKGFpcjE3LnNvdXJjZSldDQojID09PiBubyBOQSB2YWx1ZXMhDQphaXIxNy5zb3VyY2UgPT0gJycNCiMgPT0+IG5vIGVtcHR5IHN0cmluZ3MhDQoNCiMgYWlyIDIwMTggZGF0YXNldA0KaXMubmEoYWlyMTguc291cmNlKQ0KIyA9PT4gbm8gTkEgdmFsdWVzIQ0KYWlyMTguc291cmNlID09ICcnDQojID09PiBubyBlbXB0eSBzdHJpbmdzIQ0KYGBgDQoNCiMjIyBDYXN0aW5nIG9mIERhdGEgVHlwZXMNCg0KRmlyc3QsIGxldCdzIGNhc3QgdGhlIGF0dHJpYnV0ZXMgb2YgKmJpa2VkYXRhKiBpbnRvIHRoZWlyIGNvcnJlY3QgZGF0YSB0eXBlczoNCg0KYGBge3IsIGNhY2hlPVRSVUV9DQojIGNyZWF0aW5nIGNsZWFuZWQgdmVyc2lvbiBvZiBzb3VyY2UgZGF0YSB3aXRoIGNvcnJlY3RseSBjYXN0IGRhdGEgdHlwZXMgZm9yIGVhY2ggYXR0cmlidXRlDQpiaWtlZGF0YS5jIDwtIGRhdGEuZnJhbWUoDQogICAgRGF0ZSA9ICAgICAgICAgIGFzLkRhdGUoYmlrZWRhdGEuc291cmNlJERhdGUsIGZvcm1hdD0nJWQvJW0vJVknKSwNCiAgICBZZWFyID0gICAgICAgICAgeWVhcihhcy5EYXRlKGJpa2VkYXRhLnNvdXJjZSREYXRlLCBmb3JtYXQ9JyVkLyVtLyVZJykpLA0KICAgIFJlbnRDb3VudCA9ICAgICBiaWtlZGF0YS5zb3VyY2UkUmVudGVkLkJpa2UuQ291bnQsDQogICAgSG91ciA9ICAgICAgICAgIGJpa2VkYXRhLnNvdXJjZSRIb3VyLA0KICAgIFRlbXBlcmF0dXJlID0gICBhcy5kb3VibGUoYmlrZWRhdGEuc291cmNlJFRlbXBlcmF0dXJlLi5DLiwgbGVuZ3RoID0gMSksDQogICAgSHVtaWRpdHkgPSAgICAgIGJpa2VkYXRhLnNvdXJjZSRIdW1pZGl0eS4uLiwNCiAgICBXaW5kc3BlZWQgPSAgICAgYXMuZG91YmxlKGJpa2VkYXRhLnNvdXJjZSRXaW5kLnNwZWVkLi5tLnMuLCBsZW5ndGggPSAxKSwNCiAgICBWaXNpYmlsaXR5ID0gICAgYmlrZWRhdGEuc291cmNlJFZpc2liaWxpdHkuLjEwbS4sDQogICAgRGV3UG9pbnRUZW1wID0gIGFzLmRvdWJsZShiaWtlZGF0YS5zb3VyY2UkRGV3LnBvaW50LnRlbXBlcmF0dXJlLi5DLiwgbGVuZ3RoID0gMiksDQogICAgU29sYXJSYWRpYXRpb249IGFzLmRvdWJsZShiaWtlZGF0YS5zb3VyY2UkU29sYXIuUmFkaWF0aW9uLi5NSi5tMi4sIGxlbmd0aCA9IDEpLA0KICAgIFJhaW5mYWxsID0gICAgICBhcy5kb3VibGUoYmlrZWRhdGEuc291cmNlJFJhaW5mYWxsLm1tLiwgbGVuZ3RoID0gMSksDQogICAgU25vd2ZhbGwgPSAgICAgIGFzLmRvdWJsZShiaWtlZGF0YS5zb3VyY2UkU25vd2ZhbGwuLmNtLiwgbGVuZ3RoID0gMSksDQogICAgU2Vhc29uID0gICAgICAgIGZhY3RvcihiaWtlZGF0YS5zb3VyY2UkU2Vhc29ucyksDQogICAgSG9saWRheSA9ICAgICAgIGZhY3RvcihiaWtlZGF0YS5zb3VyY2UkSG9saWRheSksDQogICAgRnVuY3Rpb25hbCA9ICAgIGZhY3RvcihiaWtlZGF0YS5zb3VyY2UkRnVuY3Rpb25pbmcuRGF5KQ0KKQ0KIyBjaGVjaw0Kc3RyKGJpa2VkYXRhLmMpDQpgYGANCg0KVGhlIGNhc3Rpbmcgb2YgZGF0YSB0eXBlcyBkaWQgbm90IHNob3cgYW55IHByb2JsZW1zIHdpdGggaW52YWxpZCBkYXRhIC8gbWlzc2luZyB2YWx1ZXMgc28gbm8gcmVtb3ZhbCAvIGNsZWFudXAgb2YgdGhhdCBkYXRhc2V0IHNlZW1zIHRvIGJlIG5lY2Vzc2FyeSBhdCB0aGlzIHBvaW50LiBJbiB0aGUgbmV4dCBzdGVwIHdlIHdpbGwgY29udGludWUgd2l0aCB0aGUgdHdvIGFpciBwYXNzZW5nZXIgZGF0YXNldHMgZm9yICoqSW5jaGVvbiBBaXJwb3J0KiouIFRoaXMgZGF0YXNldCBpcyBzcGxpdCBieSB5ZWFyLiBBZnRlciBjYXN0aW5nIGFsbCBhdHRyaWJ1dGVzIHRvIGFwcHJvcHJpYXRlIGRhdGEgdHlwZXMsIHRoZSB0d28gZGF0YSBzZXRzICgyMDE3IGFuZCAyMDE4KSB3aWxsIGJlIGNvbWJpbmVkIGludG8gb25lIGRhdGFmcmFtZSB3aXRoIHRoZSBhZGRpdGlvbiBvZiBhICp5ZWFyKiBhdHRyaWJ1dGUuDQoNCg0KYGBge3IsIGNhY2hlPVRSVUV9DQojIGJlZm9yZSBnb2luZyBmdXJ0aGVyLCB3ZSBuZWVkIHRvIHJlbW92ZSB0aGUgJ1RPVEFMJyByb3cgZnJvbSB0aGUgZGF0YSBzZXQNCmFpcjE3LmVkaXQgPC0gYWlyMTcuc291cmNlW2FpcjE3LnNvdXJjZSR0aW1lICE9ICdUb3RhbCcsXQ0KYWlyMTguZWRpdCA8LSBhaXIxOC5zb3VyY2VbYWlyMTguc291cmNlJHRpbWUgIT0gJ1RvdGFsJyxdDQoNCiMgMjAxNyBhaXIgcGFzc2VuZ2VyIGRhdGEgU2VvdWwNCmFpcjE3LmMgPC0gZGF0YS5mcmFtZSgNCiAgICBZZWFyID0gICAgICAgICAgMjAxNywNCiAgICBIb3VyID0gICAgICAgICAgYXMuaW50ZWdlcihzdWJzdHJpbmcoYWlyMTcuZWRpdCR0aW1lLCAxLCAyKSksDQogICAgQXJyaXZhbCA9ICAgICAgIGFzLmludGVnZXIoZ3N1YignLCcsICcnLCBhaXIxNy5lZGl0JEFycml2YWwpKSwNCiAgICBEZXBhcnR1cmUgPSAgICAgYXMuaW50ZWdlcihnc3ViKCcsJywgJycsIGFpcjE3LmVkaXQkRGVwYXJ0dXJlKSksDQogICAgVG90YWxBcnJpdmFsID0gIGFzLmludGVnZXIoZ3N1YignLCcsICcnLCBhaXIxNy5zb3VyY2UkQXJyaXZhbFtsZW5ndGgoYWlyMTcuc291cmNlJEFycml2YWwpXSkpLCAjIGdldCB0b3RhbCBhcnJpdmFscyBmb3IgMjAxNw0KICAgIFRvdGFsRGVwYXJ0dXJlPSBhcy5pbnRlZ2VyKGdzdWIoJywnLCAnJywgYWlyMTcuc291cmNlJERlcGFydHVyZVtsZW5ndGgoYWlyMTcuc291cmNlJERlcGFydHVyZSldKSkgIyBnZXQgdG90YWwgZGVwYXJ0dXJlcyBmb3IgMjAxNw0KKQ0KIyAyMDE4IGFpciBwYXNzZW5nZXIgZGF0YSBTZW91bA0KYWlyMTguYyA8LSBkYXRhLmZyYW1lKA0KICAgIFllYXIgPSAgICAgICAgICAyMDE4LA0KICAgIEhvdXIgPSAgICAgICAgICBhcy5pbnRlZ2VyKHN1YnN0cmluZyhhaXIxOC5lZGl0JHRpbWUsIDEsIDIpKSwNCiAgICBBcnJpdmFsID0gICAgICAgYXMuaW50ZWdlcihnc3ViKCcsJywgJycsIGFpcjE4LmVkaXQkQXJyaXZhbCkpLA0KICAgIERlcGFydHVyZSA9ICAgICBhcy5pbnRlZ2VyKGdzdWIoJywnLCAnJywgYWlyMTguZWRpdCREZXBhcnR1cmUpKSwNCiAgICBUb3RhbEFycml2YWwgPSAgYXMuaW50ZWdlcihnc3ViKCcsJywgJycsIGFpcjE4LnNvdXJjZSRBcnJpdmFsW2xlbmd0aChhaXIxOC5zb3VyY2UkQXJyaXZhbCldKSksICMgZ2V0IHRvdGFsIGFycml2YWxzIGZvciAyMDE4DQogICAgVG90YWxEZXBhcnR1cmU9IGFzLmludGVnZXIoZ3N1YignLCcsICcnLCBhaXIxOC5zb3VyY2UkRGVwYXJ0dXJlW2xlbmd0aChhaXIxOC5zb3VyY2UkRGVwYXJ0dXJlKV0pKSAjIGdldCB0b3RhbCBkZXBhcnR1cmVzIGZvciAyMDE4DQopDQpgYGANCg0KDQojIyMgRGF0YSBNZXJnZQ0KDQpBcyBhIGxhc3Qgc3RlcCBpbiB0aGUgdHJhbnNmb3JtYXRpb24gcGhhc2UsIHRoZSBjdXJyZW50bHkgc2VwYXJhdGUgZGF0YXNldHMgKmFpci4xNyosICphaXIuMTgqIGFuZCAqYmlrZWRhdGEqIHdpbGwgYmUgbWVyZ2VkIHRvZ2V0aGVyIHRvIGNyZWF0ZSBvbmUgc2luZ2xlIGRhdGEgZnJhbWUgdG8gYmUgdXNlZCBmb3IgdGhlIGZ1cnRoZXIgYW5hbHlzaXMuIEluIGEgZmlyc3Qgc3RlcCwgdGhlIHR3byAqYWlyKiogZGF0YXNldHMgd2lsbCBiZSBtZXJnZWQsIGJlZm9yZSB0aGVuIGpvaW5pbmcgdGhlbSB0byB0aGUgKmJpa2VkYXRhKiBkYXRhc2V0Lg0KDQpgYGB7ciwgY2FjaGU9VFJVRX0NCiMgY29tYmluZSBhaXIxNyBhbmQgYWlyIDE4IGludG8gYWlyLmMNCmFpci5jIDwtIHJiaW5kKGFpcjE3LmMsIGFpcjE4LmMpDQojIGFkZGluZyByZWxhdGl2ZSBjYWxjdWxhdGlvbnMgKHBlcmNlbnRhZ2Ugb2YgdG90YWwgeWVhcmx5IGFycml2YWxzL2RlcGFydHVyZXMgcGVyIGhvdXIpDQpBcnJpdmFsUGN0IDwtIGFpci5jJEFycml2YWwgLyBhaXIuYyRUb3RhbEFycml2YWwNCkRlcGFydHVyZVBjdCA8LSBhaXIuYyREZXBhcnR1cmUgLyBhaXIuYyRUb3RhbERlcGFydHVyZQ0KYWlyLmMgPC0gY2JpbmQoYWlyLmMsIEFycml2YWxQY3QsIERlcGFydHVyZVBjdCkNCiMgcmVzdWx0IGNoZWNrDQpoZWFkKGFpci5jKQ0KYGBgDQoNClRoZSBhaXIgcGFzc2VuZ2VyIGRhdGEgc2V0cyByZXF1aXJlZCBhIGJpdCBtb3JlIGF0dHJpYnV0ZSBlbmdpbmVlcmluZy4gV2UgZmlyc3QgaGFkIHRvIHJlbW92ZSB0aGUgKlRPVEFMKiByb3cgZnJvbSBib3RoIGZpbGVzICgyMDE3ICYgMjAxOCkuIEFmdGVyIHRoYXQsIHdlIGFkZGVkIGEgKlllYXIqIGF0dHJpYnV0ZSB0byBlbmFibGUgdXMgdG8gbGF0dGVyIG1lcmdlIHRoZSB0d28gZGF0YSBzZXRzLiBXZSB0aGVuIGlzb2xhdGVkIGEgc3Vic3RyaW5nIG9mIHRoZSB0aW1lIGRlc2NyaXB0aW9uICh1c2VkIHRvIGJlIGluIHRoZSBmb3JtIG9mICIwMDowMCB+IDAwOjU5IikgdG8gc3RhbmRhcmRpc2UgdGhlIHZhbHVlcyB0byB0aGUgc2FtZSB2YWx1ZXMgZm91bmQgaW4gdGhlIGJpa2Ugc2hhcmluZyBkYXRhIHNldCAoIGludGVnZXJzIGZyb20gMCB0byAyMyApLiBXZSB0aGVuIGhhZCB0byByZXBsYWNlICIsIiBjaGFyYWN0ZXJzIGZvdW5kIGluIHRoZSB0b3RhbCAqQXJyaXZhbCogYW5kICpEZXBhcnR1cmUqIGNvdW50cyBiZWZvcmUgYmVpbmcgYWJsZSB0byBjYXN0IHRoZW0gdG8gaW50ZWdlci4gRnVydGhlciwgdHdvIG5ldyBhdHRyaWJ1dGVzICpUb3RhbEFycml2YWwqIGFuZCAqVG90YWxEZXBhcnR1cmUqIHdlcmUgYWRkZWQgdG8gYm90aCB0aGUgMjAxNyBhbmQgMjAxOCBkYXRhIHdoaWNoIGhvbGQgdGhlIHRvdGFsIGFycml2YWxzIGFuZCBkZXBhcnR1cmVzIHBlciB5ZWFyIHJlc3BlY3RpdmVseS4gVGhpcyBpbmZvcm1hdGlvbiB3aWxsIGJlIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSAqKnBlcmNlbnRhZ2UqKiBvZiBhcnJpdmFscy9kZXBhcnR1cmVzIHBlciBob3VyIGNvbXBhcmVkIHRvIHRoZSB0b3RhbCBhcnJpdmFscy9kZXBhcnR1cmVzIHBlciB5ZWFyLg0KVGhlIGxhc3Qgc3RlcCBjb21iaW5lZCB0aGUgdHdvIHllYXIgMjAxNyBhbmQgMjAxOCBpbnRvIG9uZSBkYXRhIGZyYW1lICphaXIuYyouDQoNClRoZSBuZXh0IHN0ZXAgd2lsbCBtZXJnZSB0aGUgYWlyIHBhc3NlbmdlciBpbmZvcm1hdGlvbiB3aXRoIHRoZSBiaWtlIHNoYXJpbmcgZGF0YSB0byBjcmVhdGUgb25lIGRhdGEgZnJhbWUgYXMgdGhlIGJhc2lzIGZvciBmdXJ0aGVyIGFuYWx5c2lzIGFuZCBtb2RlbGxpbmcuDQoNCmBgYHtyLCBjYWNoZT1UUlVFfQ0KIyBtZXJnaW5nIHRoZSB0d28gZGF0YSBmcmFtZXMgYmFzZWQgb24gJ1llYXInIGFuZCAnSG91ciknDQpkYXRhLm0gPC0gbWVyZ2UoYmlrZWRhdGEuYywgYWlyLmMsIGJ5ID0gYygnWWVhcicsJ0hvdXInKSwgYWxsID0gVFJVRSkNCiMgc29ydGluZyB0aGUgcmVzdWx0IGJ5ICdEYXRlJyBhbmQgJ0hvdXInDQpkYXRhLm0gPC0gZGF0YS5tW29yZGVyKGRhdGEubSREYXRlLCBkYXRhLm0kSG91ciksXQ0KIyBwZXJzaXN0IHJlc3VsdGluZyAoYmFzaXMpIGRhdGFmcmFtZSBmb3IgZnVydGhlciBhbmFseXNpcyB2aWEgc2VyaWFsaXNhdGlvbg0Kc2F2ZVJEUyhkYXRhLm0sIGZpbGU9Jy4vZGIvZGF0YS5SRFMnKQ0KYGBgDQoNCg0KIyBFeHBsb3JhdG9yeSBBbmFseXNpcw0KYGBge3J9DQpzZXR3ZChoZXJlKCdHcm91cF9Xb3JrJykpDQpkYXRhIDwtIHJlYWRSRFMoJy4vZGIvZGF0YS5SRFMnKQ0KaGVhZChkYXRhKQ0KYGBgDQoNCkxldCB1cyBrbm93IGNyZWF0ZSB2YXJpb3VzIGdyYXBocyB0byBnZXQgaW5zaWdodHMgdG8gdGhlIGRhdGEuDQpXZSBhcmUgaW50ZXJlc3RlZCB0byBzZWUgaG93IHRoZSBiaWtlIHJlbnRhbCBjb3VudCBkZXZlbG9wZWQgaW4gdGhlIG1vbnRocyBmcm9tIGVuZCBvZiAyMDE3IHRvIGVuZCBvZiAyMDE4Lg0KDQpgYGB7ciwgY2FjaGU9VFJVRX0NCiNoZWFkKGRhdGEpDQoNCmRhdGEkTW9udGggPC0gYXMuRGF0ZShjdXQoZGF0YSREYXRlLA0KICBicmVha3MgPSAibW9udGgiKSkNCg0KZ2dwbG90KGRhdGEgPSBkYXRhLCBtYXBwaW5nID0gYWVzKHg9TW9udGgsIHk9UmVudENvdW50KSkrDQogIHN0YXRfc3VtbWFyeShmdW4gPSBzdW0sIGdlb20gPSAibGluZSIsIGNvbG9yPSJibHVlIikrDQogIGxhYnModGl0bGU9IkJpa2UgUmVudGFsIE1vbnRobHkiLCB5PSJCaWtlIFJlbnRhbHMiKSsNCiAgc2NhbGVfeF9kYXRlKGxhYmVscyA9ICBkYXRlX2Zvcm1hdCgiJW0vJXkiKSwgYnJlYWtzID0gIjEgbW9udGgiKQ0KDQpgYGANCkNsZWFybHkgYSBzZWFzb25hbGl0eSBpcyB2aXNpYmxlIGluIHRoZSBkYXRhLiBMZXQncyBjb21wYXJlIHRoZSBkaWZmZXJlbnQgc2Vhc29ucyBieSBjcmVhdGluZyBib3hwbG90cyBmb3IgZWFjaCBzZWFzb24uDQoNCmBgYHtyLCBjYWNoZT1UUlVFfQ0KDQojVGhpcyBzaG93cyB0aGUgaG91cmx5IGRhdGEgbm90IGRhaWx5DQpnZ3Bsb3QoZGF0YSA9IGRhdGEsIG1hcHBpbmcgPSBhZXMoeD1TZWFzb24sIHkgPSBSZW50Q291bnQpKSArDQogIGdlb21fYm94cGxvdCgpKw0KICBsYWJzKHkgPSAiQmlrZSBSZW50YWxzIikNCg0KDQpgYGANCg0KQXMgZXhwZWN0ZWQgYSBzZWFzb25hbGl0eSBvZiBiaWtlIHJlbnRhbHMgaXMgdmlzaWJsZS4gTW9yZSBiaWtlIHJlbnRhbHMgb2NjdXIgZHVyaW5nIHRoZSBzdW1tZXIgbW9udGhzIGNvbXBhcmVkIHRvIHRoZSB3aW50ZXIuIEF1dHVtbiBhbmQgc3ByaW5nIHNob3cgc2ltaWxhciBkaXN0cmlidXRpb24uDQoNCkxldCdzIHNlZSBob3cgdGhlIHJlbnRhbCBjb3VudCBiZWhhdmVzIGR1cmluZyBhIGRheS4gQXJlIGNlcnRhaW4gaG91cnMgcHJlZmVycmVkIGZvciByZW50aW5nIGEgYmlrZT8NCkluIG9yZGVyIHRvIGdldCBhIGZpcnN0IGFuc3dlciB0byB0aGUgcXVlc3Rpb24gd2Ugd2lsbCBhdmVyYWdlIG92ZXIgdGhlIGhvdXJzLg0KDQpgYGB7ciwgY2FjaGU9VFJVRX0NCmdncGxvdChkYXRhID0gZGF0YSwgbWFwcGluZyA9IGFlcyh4PWFzLmZhY3RvcihIb3VyKSwgeT1SZW50Q291bnQpKSsNCiAgc3RhdF9zdW1tYXJ5KGZ1bj0ibWVhbiIsIGdlb209ImJhciIpKw0KICBsYWJzKHkgPSAiQmlrZSBSZW50YWxzIiwgeCA9ICJIb3VyIikNCg0KYGBgDQoNCldlIGFzc3VtZSB0aGF0IHRoZSB0ZW1wZXJhdHVyZSBpbmZsdWVuY2VzIHRoZSBudW1iZXIgb2YgYmlrZXMgcmVudGVkLiBMZXQncyBjcmVhdGUgYSBzY2F0dGVycGxvdCBjb21wYXJpbmcgdGhvc2UgdHdvIHZhcmlhYmxlcy4NCg0KDQpgYGB7ciwgY2FjaGU9VFJVRSwgbWVzc2FnZT1GQUxTRX0NCg0KI1Bsb3QgdGhlIGhvdXJseSBjb3VudCB2YWx1ZXMgYmFzZWQgb24gcmVjb3JkZWQgdGVtcGVyYXR1cmVzIChpbiDCsEMpDQpnZ3Bsb3QoZGF0YSA9IGRhdGEsIG1hcHBpbmcgPSBhZXMoeSA9IFJlbnRDb3VudCwgeCA9IFRlbXBlcmF0dXJlKSkgKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpDQoNCg0KDQpgYGANCg0KVGhlIHJlc3VsdGluZyBwbG90IHNob3dzIGFuIGludGVyZXN0aW5nIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRlbXBlcmF0dXJlIGFuZCB0aGUgaG91cmx5IHJlbnRhbCBjb3VudDoNCg0KKiBUaGVyZSBzZWVtcyB0byBiZSBhIHN0cm9uZ2x5IHBvc2l0aXZlIHRyZW5kIGJldHdlZW4gLTIwIMKwQyAoYWxtb3N0IDAgcmVudGFscykgdXAgdG8gYXJvdW5kICsyNy8yOCDCsEMgKGF2ZXJhZ2Ugb2YgYXJvdW5kIDEnMDAwIHJlbnRhbHMpDQoqIEFmdGVyIHRoYXQsIGEgY2xlYXIgZGVjbGluZSBpbiB0aGUgYXZlcmFnZSBob3VybHkgcmVudGFsIGNvdW50IGlzIHZpc2libGUgYmV5b25kICsyNy8yOCDCsEMNCg0KVGhpcyBzZWVtcyBpbmhlcmVudGx5IGV4cGxhaW5hYmxlIHNpbmNlIHBlb3BsZSBhcmUgbGVzcyBsaWtlbHkgdG8gdXNlIGEgYmljeWNsZSBpbiBmdWxsIHN1bW1lciBoZWF0IGFuZCB0byBwcmVmZXIgYWx0ZXJuYXRpdmUgbW9kZXMgb2YgdHJhbnNwb3J0IChwcmVmZXJhYmx5IHdpdGggQS9DKS4NCg0KDQoNCiMgTGluZWFyIE1vZGVsbGluZw0KDQojIyMgSW1wbGVtZW50aW5nIGZpcnN0IChzaW1wbGUpIE1vZGVsDQoNCkFzIHdlIGFyZSBsb29raW5nIGF0IGNvdW50IGRhdGEgaXQgbWFrZXMgc2Vuc2UgdG8gdXNlIGEgZ2VuZXJhbGlzZWQgbGluZWFyIG1vZGVsIChHTE0pIGluIG9yZGVyIHRvIHNwZWNpZnkgdGhlIGRpc3RyaWJ1dGlvbiBvZiBvdXIgZGF0YSBhcyBhIHBvaXNzb24gYW5kIGFwcGx5IHRoZSBhcHByb3ByaWF0ZSBsaW5rIGZ1bmN0aW9uIChsb2dhcml0aG0pLg0KSW4gYSBmaXJzdCwgc2ltcGxlIG1vZGVsIHdlIHdpbGwgbG9vayBpbnRvIHRoZSBlZmZlY3Qgb2YgdGhlICoqc2Vhc29uKiogY2F0ZWdvcmljYWwgdmFyaWFibGUgKCpmYWN0b3IqIGluIFIpIG9udG8gdGhlIHRhcmdldCB2YXJpYWJsZSAqKnJlbnRhbCBjb3VudCoqLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KZ2xtLmJpa2UgPC0gZ2xtKFJlbnRDb3VudCB+IFNlYXNvbiwNCiAgICAgICAgICAgICAgICBmYW1pbHkgPSAicG9pc3NvbiIsDQogICAgICAgICAgICAgICAgZGF0YSA9IGRhdGEpDQoNCmdsbS5iaWtlDQoNCiMgQWJzb2x1dGUgY291bnQgbnVtYmVycyBwZXIgbW9kZWwNCnBhc3RlKCAgJ0ludGVyY2VwdCAoQXV0dW1uKSA9ICcsIGV4cChjb2VmKGdsbS5iaWtlKSlbMV0sDQogICAgICAgICdcblNwcmluZyBBYnNvbHV0ZSBDb3VudCA9ICcsIHJvdW5kKGV4cChjb2VmKGdsbS5iaWtlKSlbMV0gKiBleHAoY29lZihnbG0uYmlrZSkpWzJdLDApLA0KICAgICAgICAnXG5TdW1tZXIgQWJzb2x1dGUgQ291bnQgPSAnLCByb3VuZChleHAoY29lZihnbG0uYmlrZSkpWzFdICogZXhwKGNvZWYoZ2xtLmJpa2UpKVszXSwgMCksDQogICAgICAgICdcbldpbnRlciBBYnNvbHV0ZSBDb3VudCA9ICcsIHJvdW5kKGV4cChjb2VmKGdsbS5iaWtlKSlbMV0gKiBleHAoY29lZihnbG0uYmlrZSkpWzRdLCAwKQ0KKQ0KYGBgDQpBcyBzdXNwZWN0ZWQgd2hlbiBpbnZlc3RpZ2F0aW5nIHRoZSBib3hwbG90cyBiYXNlZCBvbiBzZWFzb24sIHRlbXBlcmF0dXJlIG9idmlvdXNseSBoYXMgYSBzaWduaWZpY2FudCBpbXBhY3Qgb24gdGhlIGJpa2UgcmVudGFsIGNvdW50LiBIb3dldmVyLCBhcyBhbHNvIGV2aWRlbnQgaW4gdGhlc2UgcmVzdWx0cywgdGhlcmUgc2VlbXMgdG8gYmUgb25seSBhIHJlbGF0aXZlbHkgbWlub3IgZGlmZmVyZW5jZSBiZXR3ZWVuIGF1dHVtbiwgc3ByaW5nIGFuZCBzdW1tZXIgKCQ4MTkkLCAkMC45ICogODE5ID0gNzMwJCwgJDEuMjYgKiA4MTAgPSAxMDM0JCkuIFRoZSBvbmx5IHJlbGF0aXZlbHkgbGFyZ2UgZGlmZmVyZW5jZXMgb2NjdXJzIGJldHdlZW4gdGhlc2UgdGhyZWUgc2Vhc29ucyBhbmQgd2ludGVyICgkMC4yOCAqIDgxOSA9IDIyNiQpLg0KDQojIyMgTW9kZWwgUGVyZm9ybWFuY2UNCkluIG9yZGVyIHRvIGJlIGFibGUgdG8gY29tcGFyZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2Ugb2Ygb3VyIGZpcnN0IHNpbXBsZSBtb2RlbCB0byBsYXRlciwgaW1wcm92ZWQgdmVyc2lvbnMgd2Ugd2lsbCBsb29rIGludG8gbW9kZWwgcGVyZm9ybWFuY2UgYnkgYW5hbHlzaW5nIHRoZSAqKm1lYW4gc3F1YXJlZCBlcnJvcihNU0UpKiogb2Ygb3VyIGN1cnJlbnQgbW9kZWwuDQoNCkxldCdzIHN0YXJ0IGJ5IHZpc3VhbGlzaW5nIHRoZSByZXNpZHVhbHMgKGVycm9ycykgcGVyIHNhbXBsZSBwb2ludC4gQWZ0ZXIgdGhhdCB0aGUgTVNFIHdpbGwgYmUgY2FsY3VsYXRlZC4NCmBgYHtyLCBjYWNoZT1UUlVFfQ0KcGxvdChyZXNpZChnbG0uYmlrZSkpDQojIGNhbGN1bGF0aW5nIHRoZSBNU0UgdXNpbmcgdGhlIHJlc2lkdWFscyAoZXJyb3JzKSBvZiB0aGUgc2ltcGxlIG1vZGVsDQpnbG0uYmlrZS5tc2UgPC0gbWVhbigoZGF0YSRSZW50Q291bnQtZml0dGVkKGdsbS5iaWtlKSleMikNCg0KI2RhdGEuZnJhbWUoZml0dGVkKGdsbS5iaWtlKSwgZGF0YSRSZW50Q291bnQsIGZpdHRlZChnbG0uYmlrZSkgLSBkYXRhJFJlbnRDb3VudCkNCmdsbS5iaWtlLm1zZQ0KYGBgDQoNClRoZSBwbG90IG9mIHJlc2lkdWFscyBzaG93cyBhIGZldyBpbnRlcmVzdGluZyBjaGFyYWN0ZXJpc3RpY3M6DQoNCiogdGhlIGVycm9yIGZvciB0aGUgZmlyc3QgfjIwMDAgc2FtcGxlcyBzZWVtcyB0byBiZSBzaWduaWZpY2FudGx5IHNtYWxsZXIgdGhhbiBmb3IgdGhlIHJlbWFpbmluZyA2MDAwIHNhbXBsZXMNCiogdGhlIG1vZGVsIHRlbmRzIHRvIHVuZGVyZXN0aW1hdGUgdGhlIGFjdHVhbCB2YWx1ZSBzaW5jZSByZXNpZHVhbHMgYXJlIGxhcmdlbHkgcG9zaXRpdmUgYW5kIGhhdmUgYSBsYXJnZXIgcG9zaXRpdmUgZXh0cmVtZSAoYXJvdW5kIDYwKSBjb21wYXJlZCB0byB0aGUgbmVnYXRpdmUgZXh0cmVtZSAoYXJvdW5kIC00MCkNCg0KVGhlIGNhbGN1bGF0ZWQgKipNU0UgaXMgMzI4NTY0LjUqKiAoJE1TRSA9IDEvbiAqIFxzdW0oWS1caGF0e1l9KV4yID0gMzI4NTY0LjUkKQ0KDQpJbiBvcmRlciB0byBnZXQgYSBtb3JlIHJlbGlhYmxlIG1lYXN1cmUgb2YgZml0LCB3ZSB3aWxsIGZ1cnRoZXIgY29tcHV0ZSB0aGUgdGVzdCBzZXQgTVNFIHVzaW5nICoqMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uKiogaW4gdGhlIG5leHQgcGFydDoNCmBgYHtyLCBjYWNoZT1UUlVFfQ0KIyBzcGxpdCBkYXRhc2V0IGludG8gMTAgcGFydHMNCmlkcyA8LSBzYW1wbGUoMToxMCwgbnJvdyhkYXRhKSwgcmVwbGFjZT1UUlVFICApDQpkZi50ZXN0IDwtIGRhdGEuZnJhbWUoZGF0YSwgaWQgPSByb3VuZChpZHMsMCkpDQoNCiMgZml0IG1vZGVsIHRvIGVhY2ggdHJhaW5pbmcgc2V0IGFuZCBjb21wdXRlIHRlc3QgTVNFDQptc2UgPC0gYygpDQpmb3IoaSBpbiAxOjEwKXsNCiAgZml0LnVwZGF0ZWQgPC0gdXBkYXRlKGdsbS5iaWtlLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZi50ZXN0W2RmLnRlc3RbJ2lkJ10hPWksXSkNCiAgIyBwcmVkaWN0IHVzaW5nIHRlc3Qgc2V0DQogIGZpdC50ZXN0IDwtIHByZWRpY3QoZml0LnVwZGF0ZWQsIG5ld2RhdGEgPSBkZi50ZXN0W2RmLnRlc3RbJ2lkJ109PWksXSkNCiAgIyBzdG9yZSByZXN1bHRzIGluIGRmDQogIHByZWQudGVzdCA8LSBkYXRhLmZyYW1lKCBwcmVkID0gZXhwKGZpdC50ZXN0KSwgb2JzID0gZGYudGVzdFtkZi50ZXN0WydpZCddPT1pLF0kUmVudENvdW50KQ0KDQogICMgY29tcHV0ZSBNU0UNCiAgbXNlW2ldIDwtIG1lYW4oKHByZWQudGVzdCRvYnMgLSBwcmVkLnRlc3QkcHJlZCleMikNCn0NCm1lYW4obXNlKQ0KDQpgYGANClRoaXMgeWllbGRzIGEgMTAtZm9sZCBjcm9zcyB2YWxpZGF0ZWQgdGVzdGluZyBNU0Ugb2YgKiozMjg3NTEuNCoqLCBzbGlnaHRseSBiZXR0ZXIgdGhhbiB0aGUgaW4tc2FtcGxlIC8gdHJhaW5pbmcgTVNFLg0KDQojIEV4dGVuZGVkIE1vZGVsbGluZw0KYGBge3J9DQoNCmBgYA0KDQojIFtDSEFQVEVSIE9GIENIT0lDRV0gLSBFeHBsb3JpbmcgKnBsb3RseSoNCg0KRm9yIHRoZSBjaGFwdGVyIG9mIGNob2ljZSAtIHdpdGggdGhlIGFpbSB0byB1c2UgYSBwYWNrYWdlIG5vdCBtZW50aW9uZWQgaW4gdGhlIGNvdXJzZSAtIHdlIHdpbGwgZXhwbG9yZSB0aGUgKipwbG90bHkqKiBsaWJyYXJ5LiBUaGlzIGxpYnJhcnkgYWxsb3dzIHRvIGVtYmVkIGFkdmFuY2VkLCBpbnRlcmFjdGl2ZSBjaGFydHMgaW4gbWFya2Rvd24gYnkgdXNpbmcgdGhlICpwbG90bHkuanMqIEphdmFTY3JpcHQgbGlicmFyeS4gVGhlIGRvY3VtZW50YXRpb24gZm9yIHRoaXMgbGlicmFyeSBjYW4gYmUgZm91bmQgaGVyZTogaHR0cHM6Ly9wbG90bHkuY29tL3IvDQoNCkxldHMgc3RhcnQgd2l0aCBhIHNpbXBsZSwgbXVsdGktZGltZW5zaW9uYWwgcGxvdCB1c2luZyB0aGUgKnBsb3RfbHkoKSogZnVuY3Rpb24uIFBlciBkZWZhdWx0LCBwbG90bHkgZ3JhcGhzIGFsbG93IGZvciBpbnRlcmFjdGl2ZSBzZWxlY3Rpb25zIGFuZCBmaWx0ZXJpbmcsIHdoaWNoIGlzIHZlcnkgbmljZSENCg0KYGBge3IsIGNhY2hlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojbGlicmFyeShwbG90bHkpICNpbnN0YWxsLnBhY2thZ2VzKCdwbG90bHknKQ0KDQpmaWcgPC0gcGxvdF9seShkYXRhPWRhdGEsDQogICAgICAgIHg9flRlbXBlcmF0dXJlLA0KICAgICAgICB5PX5Ib3VyLA0KICAgICAgICB6PX5SZW50Q291bnQsDQogICAgICAgIHR5cGU9InNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiKQ0KZmlnDQoNCmBgYA0KDQpUaGF0IGRvZXMgbm90IHNlZW0gdG9vIGJhZCEgV2UgaGF2ZSBxdWlja2x5IGNyZWF0ZWQgYW4gKippbnRlcmFjdGl2ZSAzRCBwbG90Kiogd2hpY2ggYWxsb3dzIHVzIHRvIGNoYW5nZSB0aGUgcGVyc3BlY3RpdmUgYW5kIGhvdmVyIG92ZXIgc2luZ2xlIGRhdGEgcG9pbnRzLiBIb3dldmVyLCB0aGUgcGxvdCBhcmVhIGlzIHJhdGhlciBzbWFsbCBhbmQgdGhlcmUgYXJlIG1hbnkgZGF0YSBwb2ludHMgdG8gYmUgZGlzcGxheWVkLiBJbiB0aGUgY3VycmVudCBmb3JtLCBpdHMgaGFyZCB0byBkaXN0aW5ndWlzaCB0aGUgc2VwYXJhdGUgcG9pbnRzLiBMZXQncyBzZWUgaWYgd2UgY2FuIGFkanVzdCB0aGUgcGxvdCBhcmVhIGFjY29yZGluZ2x5IGFuZCBjcmVhdGUgbW9yZSByb29tIGZvciB0aGUgM0QgcGxvdCB0byBzaGluZS4gSW4gb3JkZXIgdG8gc2hvdyBjYXNlIGludGVyYWN0aXZlIGNhcGFiaWxpdGllcyBvZiBwbG90bHksIHdlIHdpbGwgZnVydGhlciBhZGQgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBhbmQgdXNlIHRoaXMgdG8gY29sb3IgdGhlIGRpZmZlcmVudCBkYXRhIHBvaW50cy4gQnkgZG9pbmcgc28sIHBsb3RseSB3aWxsIGF1dG9tYXRpY2FsbHkgYWRkIGEgZmlsdGVyIGFuZCBhbGxvdyB0byBzZWxlY3QgZGlmZmVyZW50IGxldmVscyBvZiB0aGUgZmFjdG9yIHZhcmlhYmxlLg0KDQpgYGB7ciwgY2FjaGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KZmlnMiA8LSBwbG90X2x5KGRhdGE9ZGF0YSwNCiAgICAgICAgeD1+VGVtcGVyYXR1cmUsDQogICAgICAgIHk9fkhvdXIsDQogICAgICAgIHo9flJlbnRDb3VudCwNCiAgICAgICAgdHlwZT0ic2NhdHRlcjNkIiwgbW9kZT0ibWFya2VycyIsIGNvbG9yPX5TZWFzb24gI2hlcmUsIHdlIGFkZGVkIHRoZSBjb2xvciBhcmd1bWVudCBhbmQgc2V0IGl0IHRvIHRoZSAnU2Vhc29uJyBmYWN0b3INCikNCiMgaW4gb3JkZXIgdG8gYWRqdXN0IHRoZSBwbG90IGFyZWEsIHdlIGhhdmUgdG8gYWRqdXN0IHRoZSB3aWR0aCwgaGVpZ2h0IGFuZCBtYXJnaW5zIG9mIHRoZSBncmFwaCBhcyB3ZWxsIGFzIGRlYWN0aXZhdGUgYXV0b3NpemUNCmZpZzIgJT4lIGxheW91dChhdXRvc2l6ZSA9IEZBTFNFLCB3aWR0aCA9IDEwMDAsIGhlaWdodCA9IDYwMCwgbWFyZ2luID0gbGlzdChsPTUwLCByPTEwLCBiPTEwLCB0PTEwLCBwYWQ9MSkpDQoNCmBgYA0KDQpOb3cgdGhpcyBzZWVtcyBhIGxvdCBiZXR0ZXIhDQpgYGB7cn0NCg0KYGBgDQo=